BM25 算法原理
BM25 是一种关键词相关性排序算法:它综合考虑关键词是否稀有、在当前文档中出现多少次,以及文档是否过长,为文档计算相关性分数。
修订说明(核实于 2026-08-13,东八区) 本篇复核后未发现事实或计算错误——核心公式、
k1/b的作用方向、TF 饱和与长度归一化的机制均正确。补充的是几处容易造成误解的边界:
- 全篇的「词」指分词器产出的 token,不是汉字数或人类认知里的词。中文下这个区别会彻底改变 IDF 和文档长度的取值。
- 长度修正不是独立的第三个乘数,它嵌在 TF 权重的分母里,与词频耦合。
- 正文给的 IDF 是 Lucene 系的非负变体,与 BM25 原始论文的形式不同(原始形式在
df > N/2时为负)。- 「文档长度差异大就该调高
b」不成立,要看长文档是内容更丰富还是注水,只能靠评测集决定。- 「数据分析」是不是一个查询词,取决于中文分词器配置。
一句话理解
> BM25 是一种关键词相关性排序算法**:它综合考虑关键词是否稀有、在当前文档中出现多少次,以及文档是否过长,为文档计算相关性分数。**
严格地说,BM25 是一族公式而不是一个——IDF 项和长度归一化都有多个变体,不同检索引擎的实现和分数并不通用。
1. BM25 解决什么问题
最简单的关键词检索可能直接统计关键词出现次数,但这样会产生问题:
- 同一个词重复很多次,分数会无限增加。
- 常见词和稀有词被同等对待。
- 长文档更容易偶然包含查询词。
- 只重复一个查询词的文档,可能压过同时命中多个查询词的文档。
例如查询:
Python 数据分析文档 A:
Python 可以用于数据分析。文档 B:
Python 是一种编程语言。Python 生态丰富。Python 可以开发网站。虽然文档 B 中 Python 出现 3 次,但文档 A 同时命中 Python 和 数据分析,通常更相关。
BM25 的目标就是对这种关键词匹配进行更合理的评分。
2. BM25 的三个核心因素
BM25 得分
= 词的稀有程度
× 经过饱和处理的词频(其中已内含文档长度修正)对应三个核心概念:
- IDF:查询词在整个知识库中有多稀有。
- TF + k1:查询词在当前文档中出现多少次,并限制重复词的收益。
- 文档长度 + b:修正长文档更容易偶然命中关键词的问题。
长度修正不是独立的第三个乘数 上面写成"× 文档长度修正"只是为了先把三个变量拆开讲清楚,实际公式里它不是一个独立相乘的因子。看第 8 节的完整公式就明白了——长度那一项嵌在 TF 权重的分母里:
括号里那一坨才是长度修正。它的作用是改变饱和曲线的陡峭程度:文档越长,分母越大,同样的词频得到的分数越低,且饱和得越快。
为什么这个区别重要: 如果它是独立乘数,"长度惩罚"和"词频收益"就是可以分开调的两件事;而实际上它们耦合在一起——调
b会同时改变不同词频下的得分曲线形状,不只是整体缩放。这解释了为什么k1和b一起调时效果不是简单叠加。
⚠️ 全篇的「词」指的是 analyzer 产出的 token,不是自然语言意义上的词 后文所有关于"词频""文档长度"的讨论,统计单位都是分词器(analyzer / tokenizer)切出来的 token,而不是"汉字数"或"人类认知里的词"。
这在中文里差别很大。同一句"数据分析平台":
分词方式 切出来的 token 「数据分析」是一个词吗 字符级( chinese_compatible)数 / 据 / 分 / 析 / 平 / 台 ❌ 是两个不相干的字 jieba 精确模式 数据分析 / 平台 ✅ 是 jieba 搜索引擎模式 数据 / 分析 / 数据分析 / 平台 ✅ 且同时保留了细粒度 这直接决定 IDF 和文档长度的取值:字符级切分下,
|D|是汉字数,而常用字的df接近全库,IDF 趋近于 0——检索退化成"谁的常用字多谁排前面"。所以看到后文"某某词出现了 3 次"时,请理解为"在你所配置的分词器下,该 token 出现了 3 次"。换分词器,全库的 IDF 和平均文档长度都会变,已调好的
k1/b也需要重新标定。
3. TF:词频
TF 全称为 Term Frequency,表示:
一个词在当前文档中出现了多少次。
例如查询词是 Python:
文档 A:Python 是一种编程语言
文档 B:Python 入门,学习 Python 语法和 Python 开发那么:
TF(A, Python) = 1
TF(B, Python) = 3仅从词频看,文档 B 应获得更高分。
3.1 词频饱和
BM25 不会让词频线性增长。
0 次 → 1 次:意义很大
1 次 → 2 次:继续加分
2 次 → 3 次:仍然加分,但增量变小
10 次 → 11 次:增加的意义很小这称为词频饱和。
> 一个关键词出现得越多,得分越高;但后续每次重复带来的加分会逐渐减少。
4. IDF:逆文档频率
IDF 全称为 Inverse Document Frequency,表示:
一个词在整个知识库中有多稀有。
规律:
词越常见 → IDF 越低 → 加分越少
词越稀有 → IDF 越高 → 加分越多例如知识库有 10000 篇文档:
“数据库”出现在 5000 篇文档中
“倒排索引”出现在 200 篇文档中那么:
IDF(倒排索引) > IDF(数据库)因为“倒排索引”更稀有,区分能力更强。
4.1 df 的含义
df 是 Document Frequency,表示:
有多少篇文档包含这个词。
注意,IDF 看的是“包含该词的文档数量”,不是该词在所有文档中总共出现了多少次。
例如:
Milvus:出现在 5 篇文档中,每篇出现 10 次
数据库:出现在 500 篇文档中,每篇出现 1 次则:
df(Milvus) = 5
df(数据库) = 500
IDF(Milvus) > IDF(数据库)其中:
TF:当前文档中出现几次
IDF:整个知识库中有多少篇文档包含它4.2 常见 IDF 公式
[ IDF(q)=\ln\left(1+\frac{N-df(q)+0.5}{df(q)+0.5}\right) ]
其中:
N:知识库中的文档总数。df(q):包含查询词q的文档数量。df(q)越小,IDF 越高。df(q)越大,IDF 越低。
这是多个 IDF 变体中的一个**,不是唯一的 BM25 公式** 上式是 Lucene / Elasticsearch / Tantivy(因而也是
pg_search和 Milvus 的 BM25 Function)采用的形式。BM25 原始论文(Robertson 等)里的 IDF 是这样的:[ IDF_{\text{原始}}(q)=\ln\frac{N-df(q)+0.5}{df(q)+0.5} ]
差别就是外面那个
1+。它不是装饰——原始形式在df(q) > N/2时会变成负数,也就是说一个出现在超过半数文档里的词,会给包含它的文档扣分。这在理论上讲得通(这种词是反向信号),但在工程上会带来反直觉的结果:文档因为多包含了一个常见词而排名下降。加上
1+之后,整个表达式恒大于 1,ln之后恒为正,IDF 最低只会趋近于 0(无贡献),不会变成惩罚。这就是所谓的"非负 IDF 变体"。实践意义: 换引擎时别假设 BM25 分数可比。同一个查询、同一批文档,Lucene 系和某个按原始论文实现的库会给出不同的分数,甚至不同的排序。这也是 混合检索选型 里强调"融合用 RRF 而不是直接加权分数"的又一个理由——连"BM25 分数"本身都不是一个跨系统统一的量。
5. 文档长度修正
假设两篇文档中 BM25 都只出现 1 次:
文档 A:全文 100 字,主要介绍 BM25
文档 B:全文 5000 字,只顺带提到一次 BM25只看 TF:
TF(A) = 1
TF(B) = 1但文档 A 通常更相关,因为它更集中地讨论 BM25。长文档更容易偶然包含关键词,因此 BM25 会进行文档长度修正。
常用符号:
dl:当前文档长度。avgdl:知识库中文档的平均长度。
dl / avgdl > 1 → 当前文档偏长
dl / avgdl = 1 → 当前文档长度接近平均值
dl / avgdl < 1 → 当前文档偏短在关键词词频相同时,明显偏长的文档通常会受到一定惩罚。
> BM25 并不是认为短文档一定更好。长文档中如果关键词出现得足够多,仍然可能获得较高分。
6. 参数 k1
k1 控制:
词频多快进入饱和状态。
常见取值约为:
k1 = 1.2 ~ 2.0规律:
k1 小 → 词频更快饱和,重复词带来的额外收益较小
k1 大 → 更重视词频,重复词仍能带来较明显的加分例如,一个系统不希望关键词重复几十次就获得巨大优势,应倾向于使用较小的 k1。
7. 参数 b
b 控制:
文档长度对评分的影响有多大。
取值范围通常是:
0 ≤ b ≤ 1规律:
b = 0 → 完全不考虑文档长度
b 越小 → 越不在意文档长度
b 越大 → 长度修正越明显
b = 1 → 充分考虑文档长度常见默认值:
b = 0.75当知识库中文档长度差异很大时,可以先试较大的 b,让长度修正更明显。
⚠️ 但这只是一个尝试方向,不是规律。长度差异大不等于就该调高 b——关键要看长文档是"内容更丰富"还是"注水更多":
- 长文档确实包含更多有效信息(如完整的技术手册 vs 摘要)→ 惩罚它反而会压掉好结果,
b应当调低; - 长文档只是因为冗长而偶然命中更多词(如会议记录、聊天日志)→
b调高才合理。
这两种情况在"长度方差"这个统计量上看起来一模一样,所以光看长度分布决定不了 b。正确做法是在评测集上跑网格搜索(b 取 0 / 0.25 / 0.5 / 0.75 / 1.0),看 nDCG 或 MRR 怎么走。默认值 0.75 是在英文新闻语料上调出来的经验值,换语言、换领域都不保证仍然最优。
8. BM25 完整公式
对于查询 Q 和文档 D:
[ BM25(D,Q)
\sum_{q\in Q} IDF(q) \times \frac{tf(q,D)(k_1+1)} {tf(q,D)+k_1\left(1-b+b\frac{dl}{avgdl}\right)} ]
符号说明:
| 符号 | 含义 |
|---|---|
Q |
用户查询 |
D |
当前文档 |
q |
查询中的某个词 |
tf(q,D) |
查询词 q 在文档 D 中出现的次数 |
IDF(q) |
查询词 q 在知识库中的稀有程度 |
dl |
当前文档长度 |
avgdl |
所有文档的平均长度 |
k1 |
控制词频饱和速度 |
b |
控制文档长度修正强度 |
9. 多个查询词如何评分
对于查询:
Python 数据分析BM25 会分别计算每个查询词对文档的得分,然后相加:
⚠️ 这里默认了「数据分析」被切成一个** token,而这取决于分词器** 下面的推导把
Python和数据分析当作两个查询词。这个前提由分词器决定,不是理所当然的:
- jieba 精确模式下确实是一个词 → 公式如下所示,两项相加;
- 字符级分词下,它会变成
数/据/分/析四个 token → 公式变成六项相加(Python可能还会再拆),且每个单字的 IDF 都很低,整个查询的区分度大幅下降;- jieba 搜索引擎模式下会同时产出
数据/分析/数据分析→ 项数更多,且存在重复计分(同一处文本被父词和子词各算一次)。所以查询侧和入库侧必须用同一个分词器——否则查询切出
数据分析、索引里存的却是四个单字,倒排表里根本查不到这个 token,得分直接为 0。这类失败很隐蔽:不报错,只是那个词永远命中不了。
[ BM25(D,Q)
score(\text{Python},D) + score(\text{数据分析},D) ]
例如:
| 文档 | Python 得分 | 数据分析得分 | 总分 |
|---|---|---|---|
| A | 1.2 | 2.5 | 3.7 |
| B | 1.8 | 0 | 1.8 |
| C | 0 | 2.8 | 2.8 |
最终排序:
A > C > B文档 B 中 Python 即使出现多次,也会受到词频饱和限制;文档 A 同时命中两个查询词,因此总分更高。
10. 完整直觉示例
查询:
Python 数据分析文档 A:
Python 可以用于数据分析文档 B:
Python Python Python 是一种编程语言文档 A
- 命中
Python。 - 命中
数据分析。 - 如果
数据分析较稀有,它的 IDF 较高。 - 文档较短,长度惩罚较小。
- 两个查询词的得分会累加。
文档 B
- 只命中
Python。 Python出现 3 次,但词频收益会逐渐饱和。- 完全没有命中
数据分析。 - 最终总分通常低于文档 A。
11. 易错点
> 误区 1:词出现得越多,得分就按比例无限增加。 错。BM25 有词频饱和机制。
> 误区 2:IDF 看一个词总共出现了多少次。 错。IDF 主要看有多少篇文档包含这个词。
> 误区 3:长文档一定比短文档差。 错。BM25 只是对长文档进行适当修正,不是直接否定长文档。
> 误区 4:k1 控制文档长度。 错。
k1控制词频饱和,b控制文档长度修正。
12. 参数速记
k1 小 → 词频更快饱和
k1 大 → 更看重词频
b 小 → 不太考虑文档长度
b 大 → 更强地修正文档长度13. 最终总结
> BM25 对查询中的每个词分别评分,然后累加:
- 用 IDF 判断词是否稀有;
- 用 TF 和 k1 判断词在当前文档中出现多少次,并限制重复收益;
- 用 dl、avgdl 和 b 修正文档长度;
- 最后将所有查询词的得分相加,得到文档的 BM25 总分。
最简记忆:
BM25 = 稀有词加权 + 词频饱和 + 长度修正14. 自测
- TF 表示什么?
- IDF 主要依据“总出现次数”还是“包含该词的文档数量”?
- 为什么关键词重复 20 次不会获得 20 倍分数?
k1较小时,词频饱和更快还是更慢?b较大时,对长文档的修正更强还是更弱?- 为什么同时命中多个查询词的文档通常更有优势?
15. 学习边界
以下内容与 BM25 经常一起出现,但不属于 BM25 核心原理,本笔记不展开:
- 倒排索引的具体实现。
- 向量检索。
- 混合检索。
- RRF 排名融合。
- Reranker 重排。